﻿using Levfier.FileIO;
using Levfier.GUI.EditorControls;
using Levfier.GUI.EditorControls.ArithmeticLogicUnits;
using Levfier.GUI.EditorControls.Chains;
using Levfier.GUI.EditorControls.Clippers;
using Levfier.GUI.EditorControls.EQs;
using Levfier.GUI.EditorControls.etc;
using Levfier.GUI.EditorControls.Exceptions;
using Levfier.GUI.EditorControls.Filters;
using Levfier.GUI.EditorControls.Gain;
using Levfier.GUI.EditorControls.IO;
using Levfier.GUI.EditorControls.Measuring;
using Levfier.GUI.EditorControls.NoiseRedution;
using Levfier.GUI.EditorControls.Parameters;
using Levfier.GUI.EditorControls.Test;
using Levfier.GUI.EditorControls.Tubes;
using Levfier.GUI.MenuLabels;
using Levfier.GUI.Status;
using Levfier.Proccessors;
using Levfier.Proccessors.Parameters;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace Levfier.GUI
{
    /// <summary>
    /// IUpdatableが画面上で選択解除された時のイベントハンドラ
    /// </summary>
    /// <param name="sender">イベント発生源</param>
    public delegate void UnselectedEventHandler(object sender);

    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : System.Windows.Controls.UserControl
    {
        #region 定数

        /// <summary>
        /// 入力コントロールのX座標オフセット値
        /// </summary>
        private static readonly int InputControlXOffset = 24;

        /// <summary>
        /// 出力コントロールのX座標オフセット値
        /// </summary>
        private static readonly int OutputControlXOffset = 528;

        /// <summary>
        /// 入・出力コントロールのY座標係数
        /// </summary>
        private static readonly int IOControlY = 72;

        /// <summary>
        /// 入・出力コントロールのY座標オフセット値
        /// </summary>
        private static readonly int IOControlYOffset = 24;

        /// <summary>
        /// グリッドのサイズ
        /// </summary>
        private static readonly int GridSize = 24;

        /// <summary>
        /// プロセッサコントロールが見つからなかった場合の代替プロセッサ名
        /// </summary>
        private static readonly string SubstituteControlName = SymmetricGainControl.ControlName;

        #endregion

        #region プロパティ

        /// <summary>
        /// アンプ
        /// </summary>
        public Amplifier Amp { get; private set; }

        /// <summary>
        /// 選択解除イベントハンドラ
        /// </summary>
        private event UnselectedEventHandler Unselect;

        /// <summary>
        /// 摘んだ場所
        /// </summary>
        private Point dragOffset;

        /// <summary>
        /// ドラッグフラグ
        /// </summary>
        private bool dragFlag;

        /// <summary>
        /// 上流のプロセッサコントロール
        /// </summary>
        private EditorControl upperControl;

        /// <summary>
        /// 下流のプロセッサコントロール
        /// </summary>
        private ProccessorControl lowerControl;

        /// <summary>
        /// 選択された描画オブジェクト
        /// </summary>
        private IDrawable selectedObject;

        /// <summary>
        /// 初期化用のステータス群
        /// </summary>
        private IEnumerable<ControlStatus> initStatuses;

        #endregion

        /// <summary>
        /// コンストラクタ
        /// </summary>
        public MainWindow()
        {

            this.InitializeComponent();
            this.InitMenuItems();
        }

        #region ステータス保存・復元関連

        /// <summary>
        /// ステータスを生成するクラス群を取得する
        /// </summary>
        /// <returns>ステータスを生成するクラス群</returns>
        public IEnumerable<IStatusCreatable> GetStatusCreaters()
        {
            List<IStatusCreatable> createrList = new List<IStatusCreatable>();

            //キャンバス上の保存
            createrList.AddRange(this.ampEditCanvas.Children.Cast<IStatusCreatable>());
            //メインパラメータの保存
            createrList.AddRange(this.mainParameterStack.mainParameterStack.Children.Cast<IStatusCreatable>());

            return createrList;
        }

        /// <summary>
        /// 引数のステータス群で初期化を行う
        /// アンプの初期設計を行うまで、実際の初期化は保留する
        /// </summary>
        /// <param name="statuses"></param>
        public void InitStatuses(IEnumerable<ControlStatus> statuses)
        {
            this.initStatuses = statuses;

            //アンプがすでに設定されている場合、ステータスを復元する
            if(null != this.Amp)
            {
                this.LoadStatuses(this.initStatuses);
            }
        }

        /// <summary>
        /// ステータスをロードする
        /// </summary>
        public void LoadStatuses(IEnumerable<ControlStatus> statuses)
        {
            IDictionary<string, IEditorControlCreatable> editorControlCreater = new Dictionary<string, IEditorControlCreatable>();
            IDictionary<string, EditorControl> editorControlTable = new Dictionary<string, EditorControl>();
            IDictionary<string, ProccessParameter> paramTable = new Dictionary<string, ProccessParameter>();

            //全てのエディターコントロールラベルを追加すること
            List<IEditorControlCreatable> createrList = new List<IEditorControlCreatable>();
            createrList.AddRange(this.gainStack.Children.Cast<IEditorControlCreatable>());
            createrList.AddRange(this.clipperStack.Children.Cast<IEditorControlCreatable>());
            createrList.AddRange(this.filterStack.Children.Cast<IEditorControlCreatable>());
            createrList.AddRange(this.paramStack.Children.Cast<IEditorControlCreatable>());
            createrList.AddRange(this.arithmeticStack.Children.Cast<IEditorControlCreatable>());
            createrList.AddRange(this.eqStack.Children.Cast<IEditorControlCreatable>());
            createrList.AddRange(this.noiseStack.Children.Cast<IEditorControlCreatable>());
            createrList.AddRange(this.measuringStack.Children.Cast<IEditorControlCreatable>());

            //////////////////////////////////////////////////////////////////////////////

            //全てのエディタコントロールラベルをディレクトリ化する
            foreach (IEditorControlCreatable creater in createrList)
            {
                editorControlCreater.Add(((Label)creater).Content.ToString(), creater);
            }

            //エディタコントロールのみを対象に復元
            foreach (ControlStatus cStatus in statuses)
            {
                EditorControlStatus eStatus = cStatus as EditorControlStatus;
                if (null != eStatus)
                {
                    bool error = false;
                    EditorControl control;

                    if (editorControlCreater.ContainsKey(eStatus.Name)) //コントロールが存在する場合
                    {
                        control = editorControlCreater[eStatus.Name].CreateEditorControl();
                    }
                    else //コントロールが見つからなかった場合、ゲインコントロールで代替する
                    {
                        control = new FakeControl();
                        error = true;
                        MessageBox.Show("プロセッサ" + eStatus.Name + "が見つかりませんでした。このアンプファイルは違うバージョンで作られた可能性があります。", "ファイル読込みエラー", MessageBoxButton.OK, MessageBoxImage.Error);
                    }

                    control.Id = eStatus.Id;

                    //ドラッグイベントを登録する
                    this.SetDragEventHundler(control);

                    //座標設定し、キャンバスに追加
                    control.Position = eStatus.Position;
                    this.ampEditCanvas.Children.Add(control);
                    control.Update();

                    //IDをキーにしてテーブルに格納
                    editorControlTable.Add(eStatus.Id, control);

                    //プロセッサコントロールの場合、パラメータを設定する
                    ProccessorControl pControl = control as ProccessorControl;

                    if (null != pControl && !error)
                    {
                        try
                        {
                            //パラメータを復元する
                            IEnumerable<ProccessParameter> paramList = pControl.LoadParameterStatus(eStatus.ParameterStatuses);

                            //メインパラメータ復元用にテーブルに格納
                            foreach(ProccessParameter param in paramList)
                            {
                                if (null != param && null != param.Id)
                                {
                                    paramTable.Add(param.Id, param);
                                }
                            }
                        }
                        catch(NotFoundParameterStatusException ex)
                        {
                            MessageBox.Show(ex.Message + "\nこのアンプファイルは違うバージョンで作られた可能性があります。", "ファイル読込みエラー", MessageBoxButton.OK, MessageBoxImage.Error);
                        }
                    }     
                }
            }

            //リンクコントロールのみを対象に復元
            foreach (ControlStatus cStatus in statuses)
            {
                ChainControlStatus eStatus = cStatus as ChainControlStatus;
                if (null != eStatus)
                {
                    dynamic upper;
                    dynamic lower;

                    //inputはステータスとして保存されていないため、特別な処置を行う
                    if (eStatus.UpperId.Contains("Input"))
                    {
                        EditorControl control = this.GetEditorControlFromCanvas(eStatus.UpperId).First<EditorControl>();
                        if (null == control)
                        {
                            control = this.GetEditorControlFromCanvas("Input1").First<EditorControl>();
                        }
                        //チェインを作成する
                        upper = control;
                    }
                    else
                    {
                        upper = editorControlTable[eStatus.UpperId];
                    }

                    //outputはステータスとして保存されていないため、特別な処置を行う
                    if (eStatus.LowerId.Contains("Output"))
                    {
                        EditorControl control = this.GetEditorControlFromCanvas(eStatus.LowerId).First<EditorControl>();
                        if (null == control)
                        {
                            control = this.GetEditorControlFromCanvas("Output1").First<EditorControl>();
                        }
                        //チェインを作成する
                        lower = control;
                    }
                    else
                    {
                        lower = editorControlTable[eStatus.LowerId];
                    }

                    //チェインを作成する
                    this.ConnectEditorControls(upper, lower);
                }
            }

            //メインパラメータコントロールを初期化する
            this.mainParameterStack.mainParameterStack.Children.Clear();

            //メインパラメータコントロールのみを対象に復元
            foreach (ControlStatus cStatus in statuses)
            {
                MainParameterStatus mpStatus = cStatus as MainParameterStatus;
                if (null != mpStatus)
                {
                    //復元済みのパラメータのうちメインパラム化対象があれば、復元する
                    if (paramTable.ContainsKey(mpStatus.TargetId))
                    {
                        ProccessParameter param = paramTable[mpStatus.TargetId];
                        this.mainParameterStack.LoadStatus(param, mpStatus);
                    }
                }
            }
        }

        #endregion

        #region 初期化

        /// <summary>
        /// アンプを設定する
        /// アンプの持つ入出力を画面上に配置する
        /// </summary>
        public void SetAmplifier(Amplifier amplifier)
        {
            this.Amp = amplifier;

            //キャンバスへの登録は、画面スレッドに任せる
            this.Dispatcher.BeginInvoke(new Action(() =>
            {
                //入力バッファを画面に配置
                for (int i = 0; i < amplifier.InputChains.Length; i++)
                {
                    EditorControl p = new InputControl(amplifier.InputChains[i]);
                    p.Id = "Input" + i;

                    //イベントハンドラ登録
                    this.SetDragEventHundler(p);

                    //キャンバスへ登録し位置調整
                    this.ampEditCanvas.Children.Add(p);
                    p.Position = new Point(InputControlXOffset, IOControlY * i + IOControlYOffset);
                    p.Update();
                }

                //出力バッファを画面に配置
                for (int i = 0; i < amplifier.OutputChains.Length; i++)
                {
                    EditorControl p = new OutputControl(amplifier.OutputChains[i]);
                    p.Id = "Output" + i;

                    //イベントハンドラ登録
                    this.SetDragEventHundler(p);

                    //キャンバスへ登録し位置調整
                    this.ampEditCanvas.Children.Add(p);
                    p.Position = new Point(OutputControlXOffset, IOControlY * i + IOControlYOffset);
                    p.Update();
                }

                //初期化用のステータスが設定されている場合、初期化を行う
                if(null != this.initStatuses)
                {
                    this.LoadStatuses(this.initStatuses);
                }
            }));
        }

        /// <summary>
        /// メニューアイテムを初期化する
        /// 新しいアイテムを追加する場合、ここに追記すること
        /// </summary>
        private void InitMenuItems()
        {
            //ゲイン
            this.RegisterMenuItemToStackPanel<SymmetricGainControl>(this.gainStack, SymmetricGainControl.ControlName);
            this.RegisterMenuItemToStackPanel<ASymmetricGainControl>(this.gainStack, ASymmetricGainControl.ControlName);
            this.RegisterMenuItemToStackPanel<TestControl>(this.gainStack, TestControl.ControlName);

            //歪
            this.RegisterMenuItemToStackPanel<HardClipperControl>(this.clipperStack, HardClipperControl.ControlName);
            this.RegisterMenuItemToStackPanel<MiddleClipperControl>(this.clipperStack, MiddleClipperControl.ControlName);
            this.RegisterMenuItemToStackPanel<SoftClipperControl>(this.clipperStack, SoftClipperControl.ControlName);
            this.RegisterMenuItemToStackPanel<OriginalTubeControl>(this.clipperStack, OriginalTubeControl.ControlName);

            //フィルタ
            this.RegisterMenuItemToStackPanel<LowPassFIRControl>(this.filterStack, LowPassFIRControl.ControlName);
            this.RegisterMenuItemToStackPanel<HighPassFIRControl>(this.filterStack, HighPassFIRControl.ControlName);
            this.RegisterMenuItemToStackPanel<LowPassIIRControl>(this.filterStack, LowPassIIRControl.ControlName);
            this.RegisterMenuItemToStackPanel<HighPassIIRControl>(this.filterStack, HighPassIIRControl.ControlName);
            this.RegisterMenuItemToStackPanel<BandPassIIRControl>(this.filterStack, BandPassIIRControl.ControlName);
            this.RegisterMenuItemToStackPanel<BandStopIIRControl>(this.filterStack, BandStopIIRControl.ControlName);
            this.RegisterMenuItemToStackPanel<LowShelfIIRControl>(this.filterStack, LowShelfIIRControl.ControlName);
            this.RegisterMenuItemToStackPanel<HighShelfIIRControl>(this.filterStack, HighShelfIIRControl.ControlName);
            this.RegisterMenuItemToStackPanel<PeakIIRControl>(this.filterStack, PeakIIRControl.ControlName);

            //算術演算器
            this.RegisterMenuItemToStackPanel<AdderControl>(this.arithmeticStack, AdderControl.ControlName);

            //EQ
            this.RegisterMenuItemToStackPanel<ThreeBandEQControl>(this.eqStack, ThreeBandEQControl.ControlName);
            this.RegisterMenuItemToStackPanel<TenBandEQControl>(this.eqStack, TenBandEQControl.ControlName);
            this.RegisterMenuItemToStackPanel<ThirtyBandEQControl>(this.eqStack, ThirtyBandEQControl.ControlName);

            //Noise Reduction
            this.RegisterMenuItemToStackPanel<SpectralSubtractionControl>(this.noiseStack, SpectralSubtractionControl.ControlName);      
            this.RegisterMenuItemToStackPanel<SpectralGateControl>(this.noiseStack, SpectralGateControl.ControlName);
            this.RegisterMenuItemToStackPanel<SlantSpectralSubtractionControl>(this.noiseStack, SlantSpectralSubtractionControl.ControlName);
            this.RegisterMenuItemToStackPanel<LearningSpectralSubtractionControl>(this.noiseStack, LearningSpectralSubtractionControl.ControlName);

            //測定器
            this.RegisterMenuItemToStackPanel<SpectrumAnalyzerControl>(this.measuringStack, SpectrumAnalyzerControl.ControlName);      

            //パラメータスタック
            this.RegisterMenuItemToStackPanel<SpinBoxControl>(this.paramStack, SpinBoxControl.ControlName);
            //this.RegisterMenuItemToStackPanel<KnobControl>(this.paramStack, KnobControl.ControlName);
        }

        /// <summary>
        /// メニューアイテムをスタックパネルに登録
        /// イベントハンドラを登録
        /// </summary>
        private void RegisterMenuItemToStackPanel<T>(Panel target, string labelName) where T : EditorControl, new()
        {
            EditorMenuLabel<T> temp = new EditorMenuLabel<T>(labelName);
            temp.FontSize = 12;
            temp.Height = 20;
            temp.Padding = new Thickness(20,0,0,0);
            target.Children.Add(temp);
            temp.MouseLeftButtonDown += this.Label_MouseDown;
        }

        #endregion

        #region メニュ -> エディタコントロール

        /// <summary>
        /// ツリーのアイテムをドラッグ開始する
        /// </summary>
        /// <param name="sender">イベント発生源</param>
        /// <param name="e">イベント詳細</param>
        private void Label_MouseDown(object sender, MouseButtonEventArgs e)
        {
            //マウスの左ボタンだけが押されている時のみドラッグできるようにする
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                Label creater = sender as Label;
                DataObject dataObject = new DataObject(typeof(IEditorControlCreatable), creater);
                DragDrop.DoDragDrop(creater, dataObject, DragDropEffects.Move);
            }
            this.SetChainMode(false);
        }

        /// <summary>
        /// キャンバスにツリーからアイテムをドロップしたときの処理
        /// ラベルの内容により、生成するアイテムを変える
        /// </summary>
        /// <param name="sender">イベント発生源</param>
        /// <param name="e">イベント詳細</param>
        private void Canvas_Drop(object sender, DragEventArgs e)
        {
            //ドラッグされたものがラベルの場合
            if (e.Data.GetDataPresent(typeof(IEditorControlCreatable)))
            {
                Canvas canvas = sender as Canvas;
                IEditorControlCreatable creater = e.Data.GetData(typeof(IEditorControlCreatable)) as IEditorControlCreatable;
                //IProccessorControlCreatable creater = label as IProccessorControlCreatable;

                //ラベルからプロセッサコントロールを生成する
                EditorControl proccessorControl = creater.CreateEditorControl();

                //ドラッグイベントを登録する
                this.SetDragEventHundler(proccessorControl);

                //位置調整して、キャンバスに追加
                Point p = e.GetPosition(canvas);
                p.X -= proccessorControl.Width / 2.0;
                p.Y -= proccessorControl.Height / 2.0;

                //グリッドモード中の場合
                if ((bool)this.gridButton.IsChecked)
                {
                    this.SetPointGridNearAxis(ref p);
                }

                proccessorControl.Position = p;
                canvas.Children.Add(proccessorControl);
                proccessorControl.Update();
            }
        }

        #endregion

        #region プロセッサコントロール

        /// <summary>
        /// プロセッサコントロールにドラッグ機能を追加する
        /// </summary>
        /// <param name="element">プロセッサコントロール</param>
        private void SetDragEventHundler(EditorControl element)
        {
            element.MouseLeftButtonDown += EditroControl_MouseLeftButtonDown;
            element.MouseLeftButtonUp += EditorControl_MouseLeftButtonUp;
            element.MouseMove += EditorControl_MouseMove;
            this.Unselect += element.Unselect;
        }

        /// <summary>
        /// 左ボタン押下時の処理
        /// ドラッグの開始処理を行う
        /// </summary>
        /// <param name="sender">イベント発生源</param>
        /// <param name="e">イベント詳細</param>
        private void EditroControl_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            UIElement element = sender as UIElement;

            #region 接続に関する処理

            if ((bool)this.chainButton.IsChecked)
            {
                //上流のプロセッサコントロールを覚える
                this.upperControl = sender as EditorControl;
                return;
            }
            #endregion

            #region ドラッグに関する処理

            this.dragFlag = true;
            //摘んだ位置を覚える
            dragOffset = e.GetPosition(element);
            element.CaptureMouse();
            #endregion

            #region 選択に関する処理

            IDrawable drawer = sender as IDrawable;
            if (null != drawer)
            {
                this.SelectDrawer(drawer);
            }

            #endregion
        }

        /// <summary>
        /// プロセッサコントロールをマウス左アップしたときの動作
        /// プロセッサコントロール同士を接続する
        /// </summary>
        /// <param name="sender">イベント発生源</param>
        /// <param name="e">イベント詳細</param>
        private void EditorControl_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            UIElement element = sender as UIElement;

            #region 接続に関する処理

            if ((bool)this.chainButton.IsChecked)
            {
                //下流のプロセッサコントロールを覚える
                this.lowerControl = sender as ProccessorControl;

                //プロセッサコントロールが選択されていない場合
                if (null == this.upperControl || null == this.lowerControl)
                {
                    this.SetChainMode(false);
                    return;
                }

                //同じプロセッサを選んでいた場合
                if (this.upperControl.Equals(lowerControl))
                {
                    this.SetChainMode(false);
                    this.upperControl = null;
                    return;
                }

                if (null != this.upperControl as ProccessorControl)
                {
                    //上流がプロセッサコントロールの場合
                    this.ConnectEditorControls(this.upperControl as ProccessorControl, this.lowerControl);
                }
                else if (null != this.upperControl as ParameterControl)
                {
                    //上流がパラメータコントロールの場合
                    this.ConnectEditorControls(this.upperControl as ParameterControl, this.lowerControl);
                }

                //チェインモードを解除
                this.SetChainMode(false);
                this.upperControl = null;
                return;
            }
            #endregion

            #region ドラッグに関する処理

            if (this.dragFlag)
            {
                element.ReleaseMouseCapture();
                dragFlag = false;
            }
            #endregion
        }

        /// <summary>
        /// プロセッサコントロールを移動させたときの動作
        /// </summary>
        /// <param name="sender">イベント発生源</param>
        /// <param name="e">イベント詳細</param>
        private void EditorControl_MouseMove(object sender, MouseEventArgs e)
        {
            //ドラッグ中の場合
            if (this.dragFlag)
            {
                EditorControl control = sender as EditorControl;
                if (null != control)
                {
                    //親のキャンバスに対するマウスの相対位置を取得
                    Point point = Mouse.GetPosition(this.ampEditCanvas);
                    point.X -= this.dragOffset.X;
                    point.Y -= this.dragOffset.Y;

                    //グリッドモード中の場合
                    if((bool)this.gridButton.IsChecked)
                    {
                        this.SetPointGridNearAxis(ref point);
                    }
                    control.Position = point;
                    control.Update();
                }
            }
        }

        /// <summary>
        /// 入力座標をグリッドの最も近い座標に変換する
        /// </summary>
        /// <param name="point">ポイント</param>
        private void SetPointGridNearAxis(ref Point point)
        {
            int x = (int)(point.X + GridSize / 2) / GridSize;
            int y = (int)(point.Y + GridSize / 2) / GridSize;
            point.X = x * GridSize;
            point.Y = y * GridSize;
        }

        #endregion

        #region チェイン

        /// <summary>
        /// ラインボタンを押下した時の動作
        /// プロセッサコントロールの接続モードに移行する
        /// </summary>
        /// <param name="sender">イベント発生源</param>
        /// <param name="e">イベント詳細</param>
        private void ChainButton_Click(object sender, RoutedEventArgs e)
        {
            //反転させる
            this.SetChainMode((bool)this.chainButton.IsChecked);
        }

        /// <summary>
        /// プロセッサ同士をチェインで接続する
        /// </summary>
        /// <param name="upperControl">上流プロセッサ</param>
        /// <param name="lowerControl">下流プロセッサ</param>
        private void ConnectEditorControls(ProccessorControl upperControl, ProccessorControl lowerControl)
        {
            //チェイン生成
            LinkControl chainControl = null;

            try
            {
                chainControl = new BufferChainControl(upperControl, lowerControl, VSTParameterManager.GetInstance().SampleCount);

                //一番下のレイヤに挿入
                this.ampEditCanvas.Children.Insert(0, chainControl);
                Canvas.SetLeft(chainControl, 0);
                Canvas.SetTop(chainControl, 0);

                //イベントハンドラ登録
                chainControl.MouseLeftButtonDown += LinkControl_MouseLeftButtonDown;
                this.Unselect += chainControl.Unselect;
            }
            catch (RouteException ex)
            {
                //ループ発生時 or 接続方向不正
                MessageBox.Show(ex.Message + " 接続を取り消します。", "接続エラー", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        /// <summary>
        /// パラメータコントロール -> プロセッサコントロール 接続する
        /// </summary>
        /// <param name="upperControl">パラメータコントロール</param>
        /// <param name="lowerControl">制御対象のプロセッサコントロール</param>
        private void ConnectEditorControls(ParameterControl upperControl, ProccessorControl lowerControl)
        {
            //チェイン生成
            LinkControl paramControl = new ParameterChainControl(upperControl, lowerControl);

            //一番下のレイヤに挿入
            this.ampEditCanvas.Children.Insert(0, paramControl);
            Canvas.SetLeft(paramControl, 0);
            Canvas.SetTop(paramControl, 0);

            //イベントハンドラ登録
            paramControl.MouseLeftButtonDown += LinkControl_MouseLeftButtonDown;
            this.Unselect += paramControl.Unselect;
        }

        /// <summary>
        /// キャンバス上で右ボタンを押下した際の動作
        /// ラインモードを解除する
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Canvas_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            if ((bool)this.chainButton.IsChecked)
            {
                this.SetChainMode(false);
            }
        }

        /// <summary>
        /// チェインモードを設定する
        /// </summary>
        /// <param name="enable">モードかいなか</param>
        private void SetChainMode(bool enable)
        {
            this.chainButton.IsChecked = enable;
            if (enable)
            {
                this.ampEditCanvas.Cursor = Cursors.Cross;
            }
            else
            {
                this.ampEditCanvas.Cursor = Cursors.Arrow;
            }
        }

        #endregion

        #region 選択

        /// <summary>
        /// 描画オブジェクトを選択した時の動作
        /// 他のオブジェクトは選択解除する
        /// </summary>
        /// <param name="drawer">描画オブジェクト</param>
        private void SelectDrawer(IDrawable drawer)
        {
            //まだ選択されていない場合
            if (this.selectedObject != drawer)
            {
                if (null != this.Unselect)
                {
                    //他のオブジェクトは選択解除するをイベント発生させる
                    this.Unselect(this);
                }
                this.selectedObject = drawer;
                this.selectedObject.Select(this);

                EditorControl propertyEditor = drawer as EditorControl;
                if (null != propertyEditor)
                {
                    this.DisplayPropertyEditorView(propertyEditor);
                }
            }
            else //すでに選択されていた場合
            {
                //解除
                this.selectedObject.Unselect(this);
                this.selectedObject = null;

                //プロパティスタックを初期化
                this.propertyArea.Child = null;
                this.propertyArea.Visibility = System.Windows.Visibility.Hidden;
            }
        }

        /// <summary>
        /// プロパティエディター画面を表示する
        /// </summary>
        /// <param name="propertyEditor">プロパティエディター画面を生成できるクラス</param>
        private void DisplayPropertyEditorView(EditorControl propertyEditor)
        {
            //プロパティスタックを初期化
            this.propertyArea.Child = null;
            this.propertyArea.Visibility = System.Windows.Visibility.Hidden;

            //ビューを表示
            UserControl view = propertyEditor.GetPropertyEditorView();

            if (null != view)
            {
                this.propertyArea.Child = view;
                this.propertyArea.Visibility = System.Windows.Visibility.Visible;
            }
        }

        /// <summary>
        /// 線が選択された時の動作
        /// </summary>
        /// <param name="sender">イベント発生源</param>
        /// <param name="e">イベント</param>
        private void LinkControl_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            IDrawable drawer = sender as IDrawable;

            if (null != drawer)
            {
                this.SelectDrawer(drawer);
            }
        }

        #endregion

        #region 削除

        /// <summary>
        /// 削除ボタンを押下した時の動作
        /// </summary>
        /// <param name="sender">イベント発生源</param>
        /// <param name="e">イベント</param>
        private void DeleteButton_Click(object sender, RoutedEventArgs e)
        {
            if (null != this.selectedObject)
            {
                this.DeleteDrawerObject(this.selectedObject);
                this.selectedObject = null;
            }
        }


        /// <summary>
        /// 描画オブジェクトをキャンバス上から削除する
        /// </summary>
        /// <param name="target">削除対象</param>
        private void DeleteDrawerObject(IDrawable target)
        {
            try
            {
                //全ての描画オブジェクトを未選択にする
                this.Unselect(this);

                //プロパティ編集画面を閉じる
                this.propertyArea.Child = null;
                this.propertyArea.Visibility = System.Windows.Visibility.Hidden;

                //削除ターゲットをキャンバス上から消去
                IEnumerable<UIElement> deleteTargets = target.Delete();
                foreach (UIElement element in deleteTargets)
                {
                    this.ampEditCanvas.Children.Remove(element);
                }

                //イベントハンドラを削除
                IEnumerable<IDrawable> deleteDrawerTargets = deleteTargets.Cast<IDrawable>();
                foreach (IDrawable drawer in deleteDrawerTargets)
                {
                    this.Unselect -= drawer.Unselect;
                }
            }
            catch (EditOperationException ex)
            {
                //ループ発生時 or 接続方向不正
                MessageBox.Show(ex.Message, "編集エラー", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        /// <summary>
        /// キャンバス上から全てのコントロールを削除する
        /// input・outputは削除しない
        /// </summary>
        private void DeleteAllControlFromCanvas()
        {
            IDrawable[] array = this.ampEditCanvas.Children.Cast<IDrawable>().ToArray<IDrawable>();
            for (int i = array.Length - 1; i >= 0; i--)
            {
                //エディターコントロールのみを削除する。ついでに関連チェインも削除されるため
                EditorControl control = array[i] as EditorControl;
                if (null != control)
                {
                    //input・outputは削除しない
                    if (!(control is OutputControl) && !(control is InputControl))
                    {
                        this.DeleteDrawerObject(control);
                    }
                }
            }
        }

        #endregion

        #region ファイル保存関連

        /// <summary>
        /// セーブボタンを押下した際の処理
        /// ファイルに名前をつけて保存する
        /// </summary>
        /// <param name="sender">イベント発生源</param>
        /// <param name="e">イベント詳細</param>
        private void SaveMenu_Click(object sender, RoutedEventArgs e)
        {
            System.Windows.Forms.SaveFileDialog dialog = new System.Windows.Forms.SaveFileDialog();
            dialog.Filter = "Amp File(*.amp)|*.amp";
            dialog.Title = "Choose a location to save a file";
            dialog.RestoreDirectory = true;

            //ダイアログを表示する
            if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                IEnumerable<IStatusCreatable> createrList = this.GetStatusCreaters();
                AmplifierFile file = new AmplifierFile();
                file.WriteToFile(dialog.FileName, createrList);
            }
        }

        /// <summary>
        /// ロードボタンを押下した際の処理
        /// ファイルを読み込む
        /// </summary>
        /// <param name="sender">イベント発生源</param>
        /// <param name="e">イベント詳細</param>
        private void LoadMenu_Click(object sender, RoutedEventArgs e)
        {
            System.Windows.Forms.OpenFileDialog dialog = new System.Windows.Forms.OpenFileDialog();
            dialog.Filter = "Amp File(*.amp)|*.amp";
            dialog.Title = "Select a file to load";
            dialog.RestoreDirectory = true;

            //ダイアログを表示する
            if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                AmplifierFile file = new AmplifierFile();
                IEnumerable<ControlStatus> statuses = file.ReadFromFile(dialog.FileName);

                //全てのアイテムを削除
                this.DeleteAllControlFromCanvas();

                //ステータスを復元
                this.LoadStatuses(statuses);
            }
        }

        #endregion

        #region キーボード入力

        /// <summary>
        /// キャンバス上でキーボードを押下した時の動作
        /// </summary>
        /// <param name="sender">イベント発生源</param>
        /// <param name="e">イベント</param>
        private void Canvas_KeyDown(object sender, KeyEventArgs e)
        {
            if (Key.Delete == e.Key)
            {
                if (null != this.selectedObject)
                {
                    this.DeleteDrawerObject(this.selectedObject);
                    this.selectedObject = null;
                }
                e.Handled = true;
            }
        }

        #endregion

        /// <summary>
        /// IDからEditorControlを取得する
        /// </summary>
        /// <param name="id">ID</param>
        /// <returns>エディターコントロール</returns>
        private IEnumerable<EditorControl> GetEditorControlFromCanvas(string id)
        {
            List<EditorControl> list = new List<EditorControl>();
            foreach (UIElement element in this.ampEditCanvas.Children)
            {
                EditorControl control = element as EditorControl;
                if (null != control)
                {
                    list.Add(control);
                }
            }
            return list.Cast<EditorControl>().Where<EditorControl>(control => control.Id == id);
        }
    }
}
